#define _POSIX_C_SOURCE 199309L
#include "client.h"
#include "util.h"
#include "config.h"
#include <wlr/types/wlr_xdg_shell.h>
#include <string.h>
#include <time.h>

struct wl_list fstack;  /* focus order */

static const char broken[] = "broken";

static const Rule rules[] = {
    /* app_id     title       tags mask     isfloating   monitor */
    /* examples:
    { "Gimp",     NULL,       0,            1,           -1 },
    */
    { "firefox",  NULL,       1 << 8,       0,           -1 },
};

void resize(Client *c, int x, int y, int w, int h, int interact)
{
#if 0
    int min_width = 0, min_height = 0;
    struct wlr_box *bbox = interact ? &sgeom : &c->mon->w;
    client_min_size(c, &min_width, &min_height);
    c->geom.x = x;
    c->geom.y = y;
    c->geom.width = MAX(min_width + 2 * c->bw, w);
    c->geom.height = MAX(min_height + 2 * c->bw, h);
    applybounds(c, bbox);

    /* Update scene-graph, including borders */
    wlr_scene_node_set_position(c->scene, c->geom.x, c->geom.y);
    wlr_scene_node_set_position(c->scene_surface, c->bw, c->bw);
    wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw);
    wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw);
    wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw);
    wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw);
    wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw);
    wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw);
    wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw);

    /* wlroots makes this a no-op if size hasn't changed */
    c->resize = client_set_size(c, c->geom.width - 2 * c->bw,
            c->geom.height - 2 * c->bw);
#endif
}

uint32_t client_set_size(Client *c, uint32_t width, uint32_t height)
{
#ifdef XWAYLAND
    if (client_is_x11(c)) {
        wlr_xwayland_surface_configure(c->surface.xwayland,
                c->geom.x, c->geom.y, width, height);
        return 0;
    }
#endif
    return wlr_xdg_toplevel_set_size(c->surface.xdg, width, height);
}

void client_min_size(Client *c, int *width, int *height)
{
    struct wlr_xdg_toplevel *toplevel;
    struct wlr_xdg_toplevel_state *state;
#ifdef XWAYLAND
    if (client_is_x11(c)) {
        struct wlr_xwayland_surface_size_hints *size_hints;
        size_hints = c->surface.xwayland->size_hints;
        if (size_hints) {
            *width = size_hints->min_width;
            *height = size_hints->min_height;
        } else {
            *width = 0;
            *height = 0;
        }
        return;
    }
#endif
    toplevel = c->surface.xdg->toplevel;
    state = &toplevel->current;
    *width = state->min_width;
    *height = state->min_height;
}

void applybounds(Client *c, struct wlr_box *bbox)
{
    /* set minimum possible */
    c->geom.width = MAX(1, c->geom.width);
    c->geom.height = MAX(1, c->geom.height);

    if (c->geom.x >= bbox->x + bbox->width)
        c->geom.x = bbox->x + bbox->width - c->geom.width;
    if (c->geom.y >= bbox->y + bbox->height)
        c->geom.y = bbox->y + bbox->height - c->geom.height;
    if (c->geom.x + c->geom.width + 2 * c->bw <= bbox->x)
        c->geom.x = bbox->x;
    if (c->geom.y + c->geom.height + 2 * c->bw <= bbox->y)
        c->geom.y = bbox->y;
}


void
focusclient(Client *c, int lift)
{
//    struct wlr_surface *old = c->server->seat->keyboard_state.focused_surface;
    struct wlr_surface *old = NULL;
    struct wlr_keyboard *kb;
    int i;

    /* Raise client in stacking order if requested */
    if (c && lift)
        wlr_scene_node_raise_to_top(c->scene);

    if (c && client_surface(c) == old)
        return;

    /* Put the new client atop the focus stack and select its monitor */
    if (c) {
        wl_list_remove(&c->flink);
        wl_list_insert(&fstack, &c->flink);
        c->server->selmon = c->mon;
        c->isurgent = 0;

        for (i = 0; i < 4; i++)
            wlr_scene_rect_set_color(c->border[i], focuscolor);
    }

#if 0
    /* Deactivate old client if focus is changing */
    if (old && (!c || client_surface(c) != old)) {
        /* If an overlay is focused, don't focus or activate the client,
         * but only update its position in fstack to render its border with focuscolor
         * and focus it after the overlay is closed.
         * It's probably pointless to check if old is a layer surface
         * since it can't be anything else at this point. */
        if (wlr_surface_is_layer_surface(old)) {
            struct wlr_layer_surface_v1 *wlr_layer_surface =
                wlr_layer_surface_v1_from_wlr_surface(old);

            if (wlr_layer_surface->mapped && (
                        wlr_layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_TOP ||
                        wlr_layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY
                        ))
                return;
        } else {
            Client *w;
            if (old->role_data && (w = client_from_wlr_surface(old)))
                for (i = 0; i < 4; i++)
                    wlr_scene_rect_set_color(w->border[i], bordercolor);

            client_activate_surface(old, 0);
        }
    }
#endif

//    printstatus();
    wlr_idle_set_enabled(c->server->idle, c->server->seat->seat, wl_list_empty(&c->server->idle_inhibit_mgr->inhibitors));

    if (!c) {
        /* With no client, all we have left is to clear focus */
        wlr_seat_keyboard_notify_clear_focus(c->server->seat->seat);
        return;
    }

#ifdef XWAYLAND
    /* This resolves an issue where the last spawned xwayland client
     * receives all pointer activity.
     */
    if (c->type == X11Managed)
        wlr_xwayland_surface_restack(c->surface.xwayland, NULL,
                XCB_STACK_MODE_ABOVE);
#endif

#if 1
    /* Have a client, so focus its top-level wlr_surface */
    kb = wlr_seat_get_keyboard(c->server->seat->seat);
    if (kb) {
        wlr_seat_keyboard_notify_enter(c->server->seat->seat, client_surface(c),
                kb->keycodes, kb->num_keycodes, &kb->modifiers);
    } else {
        wlr_seat_keyboard_notify_enter(c->server->seat->seat, client_surface(c), NULL, 0, NULL);
    }
//    input_method_relay_set_focus(input_relay, client_surface(c));

    /* Activate the new client */
    client_activate_surface(client_surface(c), 1);
#endif
}

Client *focustop(Monitor *m)
{
    Client *c;
    wl_list_for_each(c, &fstack, flink)
        if (VISIBLEON(c, m))
            return c;
    return NULL;
}

struct wlr_surface *client_surface(Client *c)
{
#ifdef XWAYLAND
    if (client_is_x11(c))
        return c->surface.xwayland->surface;
#endif
    return c->surface.xdg->surface;
}

void setmon(Client *c, Monitor *m, unsigned int newtags)
{
    Monitor *oldmon = c->mon;

    if (oldmon == m)
        return;
    c->mon = m;

    /* TODO leave/enter is not optimal but works */
    if (oldmon) {
        wlr_surface_send_leave(client_surface(c), oldmon->wlr_output);
        arrange(oldmon);
    }
    if (m) {
        /* Make sure window actually overlaps with the monitor */
        resize(c, c->geom.x, c->geom.y, c->geom.width, c->geom.height, 0);
        wlr_surface_send_enter(client_surface(c), m->wlr_output);
        c->tags = newtags ? newtags : m->tagset[m->seltags]; /* assign tags of target monitor */
        arrange(m);
    }
    focusclient(focustop(c->server->selmon), 1);
}

Client *client_from_popup(struct wlr_xdg_popup *popup)
{
    struct wlr_xdg_surface *surface = popup->base;

    while (1) {
        switch (surface->role) {
        case WLR_XDG_SURFACE_ROLE_POPUP:
            if (!wlr_surface_is_xdg_surface(surface->popup->parent))
                return NULL;

            surface = wlr_xdg_surface_from_wlr_surface(surface->popup->parent);
            break;
        case WLR_XDG_SURFACE_ROLE_TOPLEVEL:
                return surface->data;
        case WLR_XDG_SURFACE_ROLE_NONE:
            return NULL;
        }
    }
}

int client_is_unmanaged(Client *c)
{
#ifdef XWAYLAND
    return c->type == X11Unmanaged;
#endif
    return 0;
}

void client_get_geometry(Client *c, struct wlr_box *geom)
{
#ifdef XWAYLAND
    if (client_is_x11(c)) {
        geom->x = c->surface.xwayland->x;
        geom->y = c->surface.xwayland->y;
        geom->width = c->surface.xwayland->width;
        geom->height = c->surface.xwayland->height;
        return;
    }
#endif
    wlr_xdg_surface_get_geometry(c->surface.xdg, geom);
}

void client_set_tiled(Client *c, uint32_t edges)
{
#ifdef XWAYLAND
    if (client_is_x11(c))
        return;
#endif
    wlr_xdg_toplevel_set_tiled(c->surface.xdg, edges);
}

void applyrules(Client *c)
{
    /* rule matching */
    const char *appid, *title;
    unsigned int i, newtags = 0;
    const Rule *r;
    Monitor *mon = c->server->selmon, *m;

    c->isfloating = client_is_float_type(c);
    if (!(appid = client_get_appid(c)))
        appid = broken;
    if (!(title = client_get_title(c)))
        title = broken;

    for (r = rules; r < END(rules); r++) {
        if ((!r->title || strstr(title, r->title))
                && (!r->id || strstr(appid, r->id))) {
            c->isfloating = r->isfloating;
            newtags |= r->tags;
            i = 0;
            wl_list_for_each(m, &c->server->mons, link)
                if (r->monitor == i++)
                    mon = m;
        }
    }
    wlr_scene_node_reparent(c->scene, c->server->layers[c->isfloating ? LyrFloat : LyrTile]);
    setmon(c, mon, newtags);
}

int client_is_float_type(Client *c)
{
    struct wlr_xdg_toplevel *toplevel;
    struct wlr_xdg_toplevel_state state;

#ifdef XWAYLAND
    if (client_is_x11(c)) {
        struct wlr_xwayland_surface *surface = c->surface.xwayland;
        struct wlr_xwayland_surface_size_hints *size_hints;
        if (surface->modal)
            return 1;

        for (size_t i = 0; i < surface->window_type_len; i++)
            if (surface->window_type[i] == netatom[NetWMWindowTypeDialog] ||
                    surface->window_type[i] == netatom[NetWMWindowTypeSplash] ||
                    surface->window_type[i] == netatom[NetWMWindowTypeToolbar] ||
                    surface->window_type[i] == netatom[NetWMWindowTypeUtility])
                return 1;

        size_hints = surface->size_hints;
        if (size_hints && size_hints->min_width > 0 && size_hints->min_height > 0
                && (size_hints->max_width == size_hints->min_width ||
                size_hints->max_height == size_hints->min_height))
            return 1;

        return 0;
    }
#endif

    toplevel = c->surface.xdg->toplevel;
    state = toplevel->current;
    return (state.min_width != 0 && state.min_height != 0
        && (state.min_width == state.max_width
        || state.min_height == state.max_height))
        || toplevel->parent;
}

const char *client_get_appid(Client *c)
{
#ifdef XWAYLAND
    if (client_is_x11(c))
        return c->surface.xwayland->class;
#endif
    return c->surface.xdg->toplevel->app_id;
}

const char *client_get_title(Client *c)
{
#ifdef XWAYLAND
    if (client_is_x11(c))
        return c->surface.xwayland->title;
#endif
    return c->surface.xdg->toplevel->title;
}

void setfullscreen(Client *c, int fullscreen)
{
    c->isfullscreen = fullscreen;
    c->bw = fullscreen ? 0 : borderpx;
    client_set_fullscreen(c, fullscreen);

    if (fullscreen) {
        c->prev = c->geom;
        resize(c, c->mon->m.x, c->mon->m.y, c->mon->m.width, c->mon->m.height, 0);
    } else {
        /* restore previous size instead of arrange for floating windows since
         * client positions are set by the user and cannot be recalculated */
        resize(c, c->prev.x, c->prev.y, c->prev.width, c->prev.height, 0);
    }
    arrange(c->mon);
//    printstatus();
}

void client_set_fullscreen(Client *c, int fullscreen)
{
#ifdef XWAYLAND
    if (client_is_x11(c)) {
        wlr_xwayland_surface_set_fullscreen(c->surface.xwayland, fullscreen);
        return;
    }
#endif
    wlr_xdg_toplevel_set_fullscreen(c->surface.xdg, fullscreen);
}

void client_activate_surface(struct wlr_surface *s, int activated)
{
    struct wlr_xdg_surface *surface;
#ifdef XWAYLAND
    struct wlr_xwayland_surface *xsurface;
    if (wlr_surface_is_xwayland_surface(s)
            && (xsurface = wlr_xwayland_surface_from_wlr_surface(s))) {
        wlr_xwayland_surface_activate(xsurface, activated);
        return;
    }
#endif
    if (wlr_surface_is_xdg_surface(s)
            && (surface = wlr_xdg_surface_from_wlr_surface(s))
            && surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL)
        wlr_xdg_toplevel_set_activated(surface, activated);
}

void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, uint32_t time)
{
    if (c == NULL || surface == NULL)
        return;

    struct timespec now;
    int internal_call = !time;

    if (sloppyfocus && !internal_call && c && !client_is_unmanaged(c)) {
        focusclient(c, 0);
    }

    /* If surface is NULL, clear pointer focus */
    if (!surface) {
        wlr_seat_pointer_notify_clear_focus(c->server->seat->seat);
        return;
    }

    if (internal_call) {
        clock_gettime(CLOCK_MONOTONIC, &now);
        time = now.tv_sec * 1000 + now.tv_nsec / 1000000;
    }

    /* Let the client know that the mouse cursor has entered one
     * of its surfaces, and make keyboard focus follow if desired.
     * wlroots makes this a no-op if surface is already focused */
    wlr_seat_pointer_notify_enter(c->server->seat->seat, surface, sx, sy);
    wlr_seat_pointer_notify_motion(c->server->seat->seat, time, sx, sy);
}
